﻿

using S7Cat.Enums;
using S7Cat.Tools;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace S7Cat.Models
{
    public sealed class MultipleWriter(int capacity = 4) : IDisposable
    {
        private ArrayPoolBufferWriter<byte> ByteWriter { get; init; } = new(capacity);
        private ArrayPoolBufferWriter<DataItemAddress> AddressWriter { get; init; } = new(capacity);


        public Memory<DataItemAddress> Addresses => AddressWriter.Memory;
        public Memory<byte> DataBytes => ByteWriter.Memory;


        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public MultipleWriter Write<T>(T value, DataItemAddress address) where T : unmanaged, INumber<T>
        {
            int size = address.ByteCount;
            var buffer = ByteWriter.GetFixedSpan(size);

            Unsafe.WriteUnaligned(ref buffer[0], value);
            buffer.Reverse();

            ByteWriter.Advance(size);

            AddressWriter.Write(address);
            return this;
        }

        public MultipleWriter Write<T>(T value, DataType dataType = DataType.DataBlock, ushort db = 1, ushort startAdr = 0, byte bitAdr = 255) where T : unmanaged, INumber<T>
        {
            var address = new DataItemAddress(dataType, db, startAdr, (ushort)Unsafe.SizeOf<T>(), bitAdr);
            Write(value, address);
            return this;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public MultipleWriter Write(bool value, DataItemAddress address)
        {
            ByteWriter.Write(value ? (byte)1 : (byte)0);
            AddressWriter.Write(address);
            return this;
        }

        public MultipleWriter Write(bool value, DataType dataType = DataType.DataBlock, ushort db = 1, ushort startAdr = 0, byte bitAdr = 255)
        {
            var address = new DataItemAddress(dataType, db, startAdr, 1, bitAdr);
            Write(value, address);
            return this;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public MultipleWriter WriteSpan<T>(Span<T> value, DataItemAddress address) where T : unmanaged, INumber<T>
        {
            int size = address.ByteCount;
            var buffer = ByteWriter.GetFixedSpan(size);

            var bytes = MemoryMarshal.AsBytes(value);

            var len = size / value.Length;
            if (len > 1)
            {
                for (int i = 0; i < value.Length; i++)
                {
                    bytes.Slice(i * len, len).Reverse();
                }
            }

            bytes.CopyTo(buffer);
            ByteWriter.Advance(bytes.Length);

            AddressWriter.Write(address);
            return this;
        }

        public MultipleWriter WriteSpan<T>(Span<T> value, DataType dataType = DataType.DataBlock, ushort db = 1, ushort startAdr = 0, byte bitAdr = 255) where T : unmanaged, INumber<T>
        {
            var address = new DataItemAddress(dataType, db, startAdr, (ushort)(Unsafe.SizeOf<T>() * value.Length), bitAdr);
            WriteSpan(value, address);
            return this;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public MultipleWriter WriteStruct<T>(T value, DataItemAddress address) where T : struct, IPlcStruct<T>
        {
            T.Serialize(value, ByteWriter);
            AddressWriter.Write(address);
            return this;
        }

        public MultipleWriter WriteStruct<T>(T value, DataType dataType = DataType.DataBlock, ushort db = 1, ushort startAdr = 0, byte bitAdr = 255) where T : struct, IPlcStruct<T>
        {
            var address = new DataItemAddress(dataType, db, startAdr, T.ByteCount, bitAdr);
            WriteStruct(value, address);
            return this;
        }

        private bool isDisposed = false;
        public void Dispose()
        {
            if (!isDisposed)
            {
                isDisposed = true;
                ByteWriter.Dispose();
                AddressWriter.Dispose();
                GC.SuppressFinalize(this);
            }
        }

        ~MultipleWriter()
        {
            if (!isDisposed)
            {
                Dispose();
            }
        }
    }
}
